tree grammar MainScope;

options {
  language = Java;
  output = AST;
  tokenVocab = TreeConstruction;
  ASTLabelType = InterAST;
  filter = true;
}

@header
{
  package ru.msu.cmc.sp.oberon2;
  import ru.msu.cmc.sp.oberon2.*;
  import ru.msu.cmc.sp.oberon2.exceptions.*;
}

@members {
  MethodSymbol currentMethod;
  SymbolTable symtab;
  Symbol cons;
  Scope currentScope = null;
  String source;
  ProcedureType currentPT = null;
  InterAST procedure_tree = null;
  boolean in_scope = false;
  
  public MainScope(TreeNodeStream input, SymbolTable symtab, String src) {
    this(input);
    this.symtab = symtab;
    this.source = src;
    currentScope = symtab.globals;
  }
  boolean procedure_type = false;
}

topdown 
    :   enterModule
    |   enterProcedure
    |   atoms
    |   varDeclaration
    |   typeDeclaration 
    |   constDeclaration
    |   constDeclarationValid
    |   sequence
    ;

bottomup
    :   exitProcedure
    |   atoms2
    ;
    
constDeclaration
    : ^(CONST_DECL IDENT ^(EXPR .*))
      { 
          throw new RecognitionError($IDENT, 
                            " Constant " + $IDENT.text + " was declared with a non-constant expression");
      }
    ;
constDeclarationValid
    : ^(CONST_DECL IDENT (i = iconst | r = rconst | b = bconst | c = cconst | str = strconst))
    {
        Symbol s;
        if (i != null) {
            s = new Symbol($IDENT.text, $i.type);
            s.value = $i.value;
        } else if (r != null) {
            s = new Symbol($IDENT.text, $r.type);
            s.value = $r.value;
        } else if (b != null) {
            s = new Symbol($IDENT.text, SymbolTable._boolean);
            s.value = $b.value;
        } else if (c != null) {
            s = new Symbol($IDENT.text, SymbolTable._char);
            s.value = $c.value;
        } else if (str != null) {
            s = new Symbol($IDENT.text, new ArrayType(SymbolTable._char, $str.value.length()));
            s.value = $str.value;
        } else {
            throw new RecognitionError($IDENT, "Dead code is ALIVE!!!");
        } 
        s.constant = true;
        currentScope.define(s);
    }
    ;
    
iconst returns [String value, Type type]
    : ^(INTCONST INTEGER)   { $value = $INTEGER.text; $type = SymbolTable._int;} 
    | ^(SHORTCONST INTEGER) { $value = $INTEGER.text; $type = SymbolTable._shortint;}   
    | ^(LINTCONST INTEGER)  { $value = $INTEGER.text; $type = SymbolTable._longint;}  
    ;  

rconst returns [String value, Type type]
    : ^(REALCONST REAL)     { $value = $REAL.text; $type = SymbolTable._real;} 
    | ^(LREALCONST REAL)    { $value = $REAL.text; $type = SymbolTable._longreal;} 
    ;
  
bconst returns [String value]
    : ^(BOOLCONST BOOLEAN)   { $value = $BOOLEAN.text;}  
    ; 

cconst returns [String value]
    : ^(CHARCONST CHAR)   { $value = $CHAR.text;}  
    ; 
    
strconst returns [String value]
    : ^(STRCONST STRING)   { $value = $STRING.text;}  
    ; 
sequence : ^(SEQUENCE .*) {
    in_scope = true;
};

enterModule
    :   MODULE 
        {
          currentScope = new Scope(currentScope);
        }
    ;
    
exitModule
    :   MODULE
        {
        currentScope = currentScope.getEnclosingScope();
        }
    ;

enterProcedure
    :   ^(PROC_DECL (STAR)? n = IDENT (^(VAR .*) | ^(ARG_DECL .*))* (^(rt = RET_TYPE type))? t=.*)
        {
        in_scope = true;
        MethodSymbol s = new MethodSymbol($n.text, $type.type, currentScope);
        if (rt == null) {
            currentPT = new ProcedureType($n.text, null, currentScope);
            currentPT.retType = SymbolTable._void;
        } else {
            currentPT = new ProcedureType($n.text, null, currentScope);
            currentPT.retType = $type.type;
        }
        s.tree = t;
        s.constant = true;
        currentMethod = s;
        if (currentScope.define(s) == null) {
            throw new RecognitionError($IDENT,
                              " Identificator " + $IDENT.text + " was redeclared");
        }
        currentScope = new Scope(currentScope);
        }
    ;

exitProcedure
    :   PROC_DECL
        {
        currentMethod.setArgs(currentScope.getSymbols());
        currentMethod.type = currentPT;
        currentScope = currentScope.getEnclosingScope();
        }
    ;

atoms
    : t=IDENT
       {
       ((InterAST)$t).scope = currentScope;
       cons = currentScope.resolve($IDENT.text);
       if (cons == null && in_scope) {
           throw new RecognitionError(
                         $IDENT.getLine(),
                         $IDENT.getCharPositionInLine(),
                         "Variable " + $IDENT.text + " was not declared in this scope");
       }
       }
       ->{cons != null && cons.type == SymbolTable._const}? INTEGER[cons.value]
       ->IDENT
    ;

atoms2
    : t=IDENT
       {
       ((InterAST)$t).scope = currentScope;
       }
    ;
    
typeDeclaration
    : ^(TYPE_DECL type IDENT)
    {
        if ($type.type == null) 
        {
            throw new RecognitionError($IDENT,
                "Type " + $IDENT.text + " has unknown synonymous type");
        }
        
      Symbol s = new BuiltInTypeSymbol($IDENT.text, $type.type.getTypeIndex());
        if (currentScope.define(s) == null) {
            throw new RecognitionError($IDENT,
                              " Identificator " + $IDENT.text + " was redeclared");
        }
    }
    | ^(TYPE_DECL  ^(PROCEDURE .* (^(rt = RET_TYPE type))?) n = IDENT {
          in_scope = false;
	        ProcedureType s = null;
	        if (rt == null) {
            s = new ProcedureType($n.text, null, currentScope);
            s.retType = SymbolTable._void;
        } else {
            s = new ProcedureType($n.text, null, currentScope);
            s.retType = $type.type;
        }
	        currentPT = s;
        if (currentScope.define(s) == null) {
            throw new RecognitionError($IDENT,
                              " Identificator " + $IDENT.text + " was redeclared");
        }
	      }
        )
    ;

varDeclaration :
    ^(VAR_DECL type (IDENT
    { 
        if ($type.type == null) 
        {
            throw new RecognitionError($IDENT,
                "Variable " + $IDENT.text + " has unknown returning type.");
        }
        Type t = $type.type;
        Symbol s = null;
        if (t instanceof ArrayType) {
            int length = ((ArrayType)t).length;
            if (length <= 0) {
                throw new RecognitionError($IDENT, 
                " Variable " + $IDENT.text + " has invalid array size.");
            }
        }
        if (t instanceof ProcedureType) {
            s = new MethodSymbol($IDENT.text, t, currentScope);
            ((MethodSymbol) s).variable = true;
        } else {
            s = new Symbol($IDENT.text, t);
        }
        if (currentScope.define(s) == null) {
            throw new RecognitionError($IDENT, 
                              " Identificator " + $IDENT.text + " was redeclared");
        } 
    }
    )+)
    | ^(ARG_DECL type (IDENT {
        if ($type.type == null) 
        {
            throw new RecognitionError($IDENT, 
                              "Argument " + $IDENT.text + " has unknown returning type.");
        }
        Symbol s;
        if ($type.type instanceof ProcedureType) {
            s = new MethodSymbol($IDENT.text, $type.type, currentScope);
            ((MethodSymbol)s).variable = true;
        } else {
            s = new Symbol($IDENT.text, $type.type);
        }
        s.constant = true;
        currentPT.addParam(s);
        if (currentScope.define(s) == null) {
            throw new RecognitionError($IDENT, 
                              " Identificator " + $IDENT.text + " was redeclared");
        } 
    } )+)
    | ^(VAR type (IDENT {
      if ($type.type == null) 
        {
            throw new RecognitionError($IDENT,
                              "Argument " + $IDENT.text + " has unknown returning type.");
        }
        Symbol s;
        if ($type.type instanceof ProcedureType) {
            s = new MethodSymbol($IDENT.text, $type.type, currentScope);
            ((MethodSymbol)s).variable = true;
        } else {
            s = new Symbol($IDENT.text, $type.type);
        }
        s.constant = false;
        currentPT.addParam(s);
        if (currentScope.define(s) == null) {
            throw new RecognitionError($IDENT, 
                              " Identificator " + $IDENT.text + " was redeclared");
        }
    } )+)
    |  ^(ARG_DECL_PT type (IDENT {
        if ($type.type == null) 
        {
            throw new RecognitionError($IDENT, 
                              "Argument " + $IDENT.text + " has unknown returning type.");
        }
        Symbol s;
        if ($type.type instanceof ProcedureType) {
            s = new MethodSymbol($IDENT.text, $type.type, currentScope);
            ((MethodSymbol)s).variable = true;
        } else {
            s = new Symbol($IDENT.text, $type.type);
        }
        s.constant = true;
        currentPT.addParam(s); 
    } )+)
    | ^(VAR_PT type (IDENT {
      if ($type.type == null) 
        {
            throw new RecognitionError($IDENT, 
                              "Argument " + $IDENT.text + " has unknown returning type.");
        }
        Symbol s;
        if ($type.type instanceof ProcedureType) {
            s = new MethodSymbol($IDENT.text, $type.type, currentScope);
            ((MethodSymbol)s).variable = true;
        } else {
            s = new Symbol($IDENT.text, $type.type);
        }
        s.constant = false;
        currentPT.addParam(s); 
    } )+)
    ;

inumber returns [String value, Type type]
    : ^(INTCONST INTEGER)   { $value = $INTEGER.text; $type = SymbolTable._int;} 
    | ^(SHORTCONST INTEGER) { $value = $INTEGER.text; $type = SymbolTable._shortint;}   
    | ^(LINTCONST INTEGER)  { $value = $INTEGER.text; $type = SymbolTable._longint;}  
    ;  
  
    
type returns [Type type]
  : ^(ARRAY (n = inumber)? typeElement) //warning, but it works!
    {
        int num;
        if ($n.start == null) num = -1;
        else num = Integer.parseInt($n.value);
        $type = new ArrayType($typeElement.type, num);
     }
  | typeElement {$type = $typeElement.type;}
  | wrongType
  ;

wrongType
    :   ^(ARRAY s=. k=.) 
    {
         
        throw new RecognitionError(0,0, "Wrong type");
     }
     ;

typeElement returns [Type type]
@init {InterAST t = (InterAST)input.LT(1);}
@after {
    t.symbol = currentScope.resolve(t.getText());
    t.scope = currentScope;
    if (!(t.symbol instanceof Type)) throw new RecognitionError(0, 0, t.getText() + " isn't a valid type");
    $type = (Type)t.symbol;
    if ($type == null) 
    {throw new RecognitionError(0,0, "Unknown type " + t.getText());}
}
    :   'REAL'
    |   'BYTE'
    |   'INTEGER'
    |   'LONGINT'
    |   'SHORTINT'
    |   'LONGREAL'
    |   'CHAR'
    |   'BOOLEAN'
    |   IDENT
    ;